//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <elasys_server.h>
#include "wincetypes.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include "CBasicFile.h"

#define Lock()       do{EnterCriticalSection((LPWIN_CRITICAL_SECTION)m_FileLock);}while(0)
#define Unlock()     do{LeaveCriticalSection((LPWIN_CRITICAL_SECTION)m_FileLock);}while(0)

UInt32 CBasicFile::AddRef()
{
    LONG nRef = m_cRef.Increment();
    return (ULONG)nRef;
}

UInt32 CBasicFile::Release()
{
    LONG nRef = m_cRef.Decrement();

    if (0 == nRef) {
        if (m_FileHandle != INVALID_HANDLE_VALUE) ::CloseHandle(m_FileHandle);
        delete this;
    }
    return nRef;
}

ECode CBasicFile::Aggregate(
    AggregateType type, PInterface pObj)
{
    return E_NOT_IMPLEMENTED;
}

ECode CBasicFile::GetDomain(
    IInterface **ppObj)
{
    return E_NOT_IMPLEMENTED;
}

PInterface CBasicFile::Probe(REIID riid)
{
    if (EIID_IInterface == riid) {
        return (IInterface *)(IAbsoluteIO *)this;
    }
    else if (EIID_IAbsoluteIO == riid) {
        return (IAbsoluteIO *)this;
    }
    else if (EIID_IBasicFile == riid) {
        return (IBasicFile *)this;
    }
    else if (EIID_IFileMonitor == riid) {
        return (IFileMonitor *)this;
    }
    else if (EIID_CLASS_INFO == riid) {
        return (IInterface *)&ECLSID_CKtfsFile;
    }
    else if (EIID_INTERFACE_INFO == riid) {
        return (IInterface *)&EIID_IBasicFile;
    }

    return NULL;
}

CBasicFile::CBasicFile()
{
    m_FileHandle = INVALID_HANDLE_VALUE;
    m_wstrFileName = NULL;
}

#ifdef _GNUC
const UINT64 bias64 = 0x019db1ded53e8000LL;
//1970.01.01 - 1601.01.01
#else
const UINT64 bias64 = 0x019db1ded53e8000;
//1970.01.01 - 1601.01.01
#endif

CBasicFile::CBasicFile(WString esFileName, BOOL bIsExist, BOOL bIsWritable)
{
    DWORD AccessMode = 0, ShareMode = 0;
    int AttrMode = FILE_ATTRIBUTE_NORMAL;
    int CreateMode = 0;
    wchar_t *wcsFileName = (wchar_t *)malloc(sizeof(wchar_t) * (wcslen(esFileName)+1));
    assert(wcsFileName);
    wcscpy(wcsFileName, esFileName);
    m_wstrFileName = wcsFileName;
    if (bIsExist) {
        CreateMode = OPEN_ALWAYS;
    }
    else {
        CreateMode = CREATE_ALWAYS;
    }

//    if (bIsWritable) {
    AccessMode = GENERIC_READ | GENERIC_WRITE;
//    }
//    else {
//        AccessMode = GENERIC_READ;
//    }

    ShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;

    m_FileHandle = CreateFileW(m_wstrFileName, AccessMode,
        ShareMode, NULL, CreateMode,  AttrMode, NULL);
    if (m_FileHandle == INVALID_HANDLE_VALUE) {
        int nerrcode = GetLastError();
        if (nerrcode == 32 || nerrcode == 5) {  //file is read-only or file is being used
            m_FileHandle = CreateFileW(m_wstrFileName,
                GENERIC_READ, ShareMode, NULL, CreateMode,  AttrMode, NULL);
        }
        else {
            printf("Fail to open or create file %S!\n",
                (const wchar_t *)m_wstrFileName);
            printf("error code is %d\n", nerrcode);
            return;
        }
    }
    FILETIME CreationTime;
    FILETIME LastAccessTime;
    FILETIME LastWriteTime;
    UINT64 tmpint;

    GetFileTime(m_FileHandle, &CreationTime,
        &LastAccessTime, &LastWriteTime);

    tmpint = *(UINT64*)&CreationTime;
    m_iCreateTime = (UINT32)((tmpint - bias64) / 10000000);
    tmpint = *(UINT64*)&LastAccessTime;
    m_iAccessTime = (UINT32)((tmpint - bias64) / 10000000);
    tmpint = *(UINT64*)&LastWriteTime;
    m_iModificationTime = (UINT32)((tmpint - bias64) / 10000000);
    m_eFMode = FileAccessMode_ReadWrite;

    m_FileLock = (LPWIN_CRITICAL_SECTION)malloc(sizeof(WIN_CRITICAL_SECTION));
    InitializeCriticalSection((LPWIN_CRITICAL_SECTION)m_FileLock);
}

CBasicFile::~CBasicFile()
{
    if (m_wstrFileName) {
        free((void *)m_wstrFileName);
    }
    DeleteCriticalSection((LPWIN_CRITICAL_SECTION)m_FileLock);
    free(m_FileLock);
}

ECode CBasicFile::Read(
    /* [in] */ Int32 offset,
    /* [in] */ Int32 toRead,
    /* [out] */ MemoryBuf * pBuffer)
{
    DWORD iSizeRead = 0;
    void* pBuf = NULL;

    if (pBuffer == NULL) {
        return E_INVALID_ARGUMENT;
    }
    if (toRead > pBuffer->GetCapacity() ||
        /*toRead < 0 || */toRead > 0x7fffffff ||
        toRead > pBuffer->GetCapacity()) {
        return ECODE_FROM_ERRNO(EINVAL);
    }

    Lock();
    Int32 orgSize = 0;
    GetSize(&orgSize);
    if (/*offset < 0 || */offset > orgSize) {
        Unlock();
        return ECODE_FROM_ERRNO(EINVAL);
    }

    pBuf = (char *)pBuffer->GetPayload();

    DWORD SetPointRet;
    SetPointRet = SetFilePointer(m_FileHandle, offset, NULL, FILE_BEGIN);
    if (SetPointRet == INVALID_SET_FILE_POINTER) {
        Unlock();
        return E_INVALID_POINTER;
    }

    if (ReadFile(m_FileHandle, pBuf, toRead, &iSizeRead, NULL)) {
        pBuffer->SetUsed(iSizeRead);
        Unlock();
        return NOERROR;
    }

    Unlock();
    wprintf(L"CBasicFile::Read: At file %d, line %d.\n", __FILE__, __LINE__);
    return E_INVALID_OPERATION;
}

ECode CBasicFile::Write(
    /* [in] */ Int32 offset,
    /* [in] */ const MemoryBuf & ezbBuf,
    /* [out] */ Int32 * pWrite)
{
    if (ezbBuf.IsNullOrEmpty() || NULL == pWrite) {
        return E_INVALID_ARGUMENT;
    }

    Lock();
    if (m_eFMode & FileAccessMode_Read) {
        Unlock();
        return ECODE_FROM_ERRNO(EPERM);
    }

    int Size = ezbBuf.GetUsed();
    if (Size > 0x7fffffff) {
        Unlock();
        return ECODE_FROM_ERRNO(EINVAL);
    }

    if (offset + Size > 0x70000000) {
        Unlock();
        return ECODE_FROM_ERRNO(EINVAL);
    }

    Int32 orgSize = 0;
    GetSize(&orgSize);
    if ((offset > orgSize) || (offset + Size) > orgSize) {
        SetSize(offset + Size);
    }

    DWORD SetPointRet;
    SetPointRet = SetFilePointer(m_FileHandle, offset, NULL, FILE_BEGIN);
    if (SetPointRet == INVALID_SET_FILE_POINTER) {
        Unlock();
        return E_INVALID_POINTER;
    }

    if (WriteFile(m_FileHandle, ezbBuf.GetPayload(),
        Size, (DWORD *)pWrite, NULL)) {
        Unlock();
        return NOERROR;
    }
    Unlock();
    return S_FALSE;
}

ECode CBasicFile::GetSize(
    /* [out] */ Int32 * pSize)
{
    if (!pSize) {
        return E_INVALID_ARGUMENT;
    }
    Lock();
    *pSize = GetFileSize(m_FileHandle, NULL);
    Unlock();
    return NOERROR;
}

ECode CBasicFile::SetSize(
    /* [in] */ Int32 size)
{
    Lock();
    if (m_eFMode & FileAccessMode_Read) {
        Unlock();
        return ECODE_FROM_ERRNO(EPERM);
    }

    DWORD pos = SetFilePointer(m_FileHandle, size, NULL, SEEK_SET);

    if (pos == 0xffffffff) {
        printf("CBasicFile::SetSize SetFilePointer Fail error code %d\n", GetLastError());
        Unlock();
        return E_INVALID_OPERATION;
    }
    if (SetEndOfFile(m_FileHandle)) {
        Unlock();
        return NOERROR;
    }
        printf("CBasicFile::SetSize SetEndOfFile Fail error code %d\n", GetLastError());
    Unlock();
    return E_INVALID_OPERATION;
}

ECode CBasicFile::GetOptimalBlockSize(
    /* [out] */ Int32 * pSize)
{
    if (!pSize) {
        return E_INVALID_ARGUMENT;
    }	
    *pSize = 4096;
    return NOERROR;
}

ECode CBasicFile::Sync()
{
    return NOERROR;
}

ECode CBasicFile::GetType(
    /* [out] */ FileType * pType)
{
    if (!pType) {
        return E_INVALID_ARGUMENT;
    }

    *pType = FileType_RegularFile;
    return NOERROR;
}

ECode CBasicFile::GetAccessTime(
    /* [out] */ Int32 * pTime)
{
    if (!pTime) {
        return E_INVALID_ARGUMENT;
    }
    Lock();
    *pTime = m_iAccessTime;
    Unlock();
    return NOERROR;
}

void LocalTimeToFileTime(Int32 time, FILETIME *pft)
{
    UINT64 tmpint = ((UINT64)time * 10000000 + bias64);
    pft->dwLowDateTime = (UINT32)tmpint;
    pft->dwHighDateTime = (UINT32)(tmpint>>32);
}

ECode CBasicFile::SetAccessTime(
    /* [in] */ Int32 time)
{
    Lock();
    if (m_eFMode & FileAccessMode_Read) {
        Unlock();
        return ECODE_FROM_ERRNO(EPERM);
    }

    FILETIME ft;
    LocalTimeToFileTime(time, &ft);
    if (!SetFileTime(m_FileHandle, NULL, &ft, NULL)) {
        Unlock();
        return ECODE_FROM_WIN32(GetLastError());
    }
    m_iAccessTime = time;

    Unlock();
    return NOERROR;
}

ECode CBasicFile::GetModificationTime(
    /* [out] */ Int32 * pTime)
{
    Lock();
    *pTime = m_iModificationTime;
    Unlock();
    return NOERROR;
}

ECode CBasicFile::SetModificationTime(
    /* [in] */ Int32 time)
{
    Lock();
    if (m_eFMode & FileAccessMode_Read) {
        Unlock();
        return ECODE_FROM_ERRNO(EPERM);
    }

    FILETIME ft;
    LocalTimeToFileTime(time, &ft);
    if (!SetFileTime(m_FileHandle, NULL, NULL, &ft)) {
        Unlock();
        return ECODE_FROM_WIN32(GetLastError());
    }
    m_iModificationTime = time;

    Unlock();
    return NOERROR;
}

ECode CBasicFile::GetCreateTime(
    /* [out] */ INT * pTime)
{
    if (!pTime) {
        return E_INVALID_ARGUMENT;
    }
    Lock();
    *pTime = m_iCreateTime;
    Unlock();
    return NOERROR;
}

ECode CBasicFile::CheckAccessMode(
    /* [in] */ FileAccessMode mode)
{
    Lock();
    if (m_eFMode == mode) {
        Unlock();
        return NOERROR;
    }
    Unlock();
    return S_FALSE;
}

ECode CBasicFile::GetAccessMode(
    /* [out] */ FileAccessMode * pMode)
{
    if (!pMode) {
        return E_INVALID_ARGUMENT;
    }

    Lock();
    *pMode = m_eFMode;
    Unlock();
    return NOERROR;
}

ECode CBasicFile::SetAccessMode(
    /* [in] */ FileAccessMode mode)
{
    Lock();
    wchar_t *wcsName = (wchar_t *)m_wstrFileName;
    DWORD attr = GetFileAttributesW(wcsName);
    if (mode == FileAccessMode_Read) {
        attr |= FILE_ATTRIBUTE_READONLY;
    }
    else {
        attr &= ~FILE_ATTRIBUTE_READONLY;
    }
    SetFileAttributesW(wcsName, attr);
    m_eFMode = mode;
    Unlock();
    return NOERROR;
}

ECode CBasicFile::GetShareMode(
    /* [out] */ FileShareMode * pMode)
{
    return E_NOT_IMPLEMENTED;
}

ECode CBasicFile::SetShareMode(
    /* [in] */ FileShareMode mode)
{
    return E_NOT_IMPLEMENTED;
}

ECode CBasicFile::AddFileMonitor(
    /* [out] */ IEvent **pEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode CBasicFile::RemoveFileMonitor(
    /* [in] */ IEvent *pEvent)
{
    return E_NOT_IMPLEMENTED;
}
